﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Runtime.Serialization;
using Flower.Directory.Scripting;
using Flower.Services.Data;
using Jurassic;
using Jurassic.Library;

namespace Flower.Directory.Management.Scripting
{
    public static class Printer
    {
        private static readonly Dictionary<string, string> DirEntryTypeNamesMap = new Dictionary<string, string> 
        {
            { "Assembly", "AS" },
            { "Folder", "FD" },
            { "Link", "LN" },
            { "Message", "MS" },
            { "Process", "PS" },
            { "Processor", "PR" },
            { "Role", "RL" },
            { "ServiceContainer", "SC" },
            { "Set", "SE" },
            { "State", "ST" },
            { "Workflow", "WF" }
        };

        private static readonly Dictionary<DirectoryEntryTypes, string> DirEntryTypesMap = new Dictionary<DirectoryEntryTypes, string>
        {
            { DirectoryEntryTypes.Assembly, "AS" },
            { DirectoryEntryTypes.Folder, "FD" },
            { DirectoryEntryTypes.Link, "LN" },
            { DirectoryEntryTypes.Message, "MS" },
            { DirectoryEntryTypes.Process, "PS" },
            { DirectoryEntryTypes.Processor, "PR" },
            { DirectoryEntryTypes.Role, "RL" },
            { DirectoryEntryTypes.ServiceContainer, "SC" },
            { DirectoryEntryTypes.Set, "SE" },
            { DirectoryEntryTypes.State, "ST" },
            { DirectoryEntryTypes.Workflow, "WF" }
        };

        public static void Print(TextWriter writer, object obj, bool newLine)
        {
            if (!(obj is Jurassic.Undefined))
            {
                if (obj is ArrayInstance)
                {
                    var arr = (ArrayInstance)obj;

                    for (int i = 0; i < arr.Length; i++)
                    {
                        Print(writer, arr[i], (i < arr.Length - 1) && newLine);

                        if (!newLine && i < arr.Length - 1)
                        {
                            writer.Write(", ");
                        }
                    }
                }
                else if (obj is DirectoryEntryObject)
                {
                    PrintDirectoryEntry(writer, ((DirectoryEntryObject)obj).Entry);
                }
                else if (obj is XmlObject)
                {
                    ((XmlObject)obj).WriteXml(writer);
                }
                else if (obj is ObjectInstance)
                {
                    PrintObject(writer, (ObjectInstance)obj);
                }
                else
                {
                    writer.Write(obj);
                }
            }

            if (newLine)
            {
                writer.WriteLine();
            }
        }

        private static void PrintDirectoryEntry(TextWriter writer, DirectoryEntry entry)
        {
            writer.WriteLine
            (
                "{0}[{1}]: {2}", 
                (entry is DirectoryEntryInfo) ? 
                    DirEntryTypesMap[((DirectoryEntryInfo)entry).Type] : 
                    DirEntryTypeNamesMap[entry.GetType().Name], 
                entry.Id, 
                entry.Name
            );
            writer.WriteLine("C: {0} U: {1}", entry.CreatedOn, entry.UpdatedOn);

            IEnumerable<string> readers = 
                entry
                    .AccessControlList
                    .Where(perm => perm.Permission.HasFlag(Permission.Read))
                    .Select(perm => perm.Role);
            IEnumerable<string> writers =
                entry
                    .AccessControlList
                    .Where(perm => perm.Permission.HasFlag(Permission.Write))
                    .Select(perm => perm.Role);
            IEnumerable<string> childrenCreators =
                entry
                    .AccessControlList
                    .Where(perm => perm.Permission.HasFlag(Permission.CreateChildren))
                    .Select(perm => perm.Role);

            writer.WriteLine
            (
                "R: {0} W: {1} CC: {2}", 
                string.Join(", ", readers), 
                string.Join(", ", writers),
                string.Join(", ", childrenCreators)
            );
        }

        private static void WriteXml(this XmlObject xmlObject, TextWriter writer)
        {
            using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = true }))
            {
                xmlObject.Xml.WriteTo(xmlWriter);
            }
        }

        private static string Indentation(int size)
        {
            const string ind = "  ";
            var bld = new StringBuilder();

            for (int i = 0; i < size; i++)
            {
                bld.Append(ind);
            }

            return bld.ToString();
        }

        private static void PrintObjectValue(TextWriter writer, object obj, int indSize, bool indentThisLine, bool objAtNewLine)
        {
            if (obj is ArrayInstance)
            {
                if (objAtNewLine)
                {
                    writer.WriteLine();
                }

                PrintArray(writer, (ArrayInstance)obj, indSize + 1);
            }
            else if (obj is XmlObject)
            {
                if (indentThisLine)
                {
                    writer.Write(Indentation(indSize + 1));
                }

                writer.Write("XML");
            }
            else if (obj is ObjectInstance && !(obj is DateInstance || obj is NumberInstance || obj is StringInstance || obj is BooleanInstance))
            {
                if (objAtNewLine)
                {
                    writer.WriteLine();
                }

                PrintObject(writer, (ObjectInstance)obj, indSize + 1);
            }
            else if (obj is string || obj is StringInstance)
            {
                if (indentThisLine)
                {
                    writer.Write(Indentation(indSize + 1));
                }

                writer.Write("'");
                writer.Write(obj.ToString());
                writer.Write("'");
            }
            else
            {
                if (indentThisLine)
                {
                    writer.Write(Indentation(indSize + 1));
                }

                writer.Write(obj.ToString());
            }
        }

        private static void PrintObject(TextWriter writer, ObjectInstance obj, int indSize = 0)
        {
            writer.WriteLine(Indentation(indSize) + "{");

            foreach (PropertyNameAndValue prop in obj.Properties)
            {
                if (prop.Value is FunctionInstance)
                {
                    continue;
                }

                writer.Write("{0}{1}: ", Indentation(indSize + 1), prop.Name);

                PrintObjectValue(writer, prop.Value, indSize, false, true);
                writer.WriteLine(",");
            }

            writer.Write(Indentation(indSize) + "}");
        }

        private static void PrintArray(TextWriter writer, ArrayInstance arr, int indSize = 0)
        {
            writer.WriteLine(Indentation(indSize) + "[");

            foreach (object el in arr.ElementValues)
            {
                PrintObjectValue(writer, el, indSize, true, false);
                writer.WriteLine(",");
            }

            writer.Write(Indentation(indSize) + "]");
        }
    }
}
